package puf.m2.automaton;

import java.util.ArrayList;
import java.util.List;

public class Tile {
    private Word<Letter> root;
    private Word<Letter> left; // left constraint
    private Word<Letter> right; // right constraint
    private Direction direction;

    public Tile(Word<Letter> root, Word<Letter> left, Word<Letter> right, Direction direction)
            throws IllegalArgumentException {
        if (root == null || left == null ||
                right == null || direction == null) {
            throw new IllegalArgumentException("Parameters should not be null");
        }
        this.root = root;
        this.left = left;
        this.right = right;
        this.direction = direction;
    }

    /*----------- METHODS ------------*/
    /**
     * Compute the production of 2 tiles with the checking the compatibilities
     * before performing the computation. Example:
     *   <code>u = (u1, u2, u3)</code> and <code>v = (v1, v2, v3)</code>,
     * then they should match the conditions
     *   <code>(u1.u2 V<sub>s</sub> v1 <> 0) and (u3 V<sub>p</sub> v2.v3 <> 0)</code>
     * @param aTile
     * @return the product tile
     */
    public Tile concat(Tile aTile) {

        Tile newTile = null;

        // undefined tile
        if (aTile == null) {
            return newTile;
        }

        Word<Letter> u1 = this.left;
        Word<Letter> u2 = this.root;
        Word<Letter> u3 = this.right;
        Word<Letter> v1 = aTile.left();
        Word<Letter> v2 = aTile.root();
        Word<Letter> v3 = aTile.right();

        // checking compatibility between the current tile and aTile
        Word<Letter> leftMatching = (u1.concat(u2)).jointSuffix(v1);
        Word<Letter> rightMatching = u3.jointPrefix(v2.concat(v3));
        if (leftMatching == null || rightMatching == null) {
            return null;
        }
        // 2 tiles are compatible otherwise
        Word<Letter> newLeft = leftMatching.concat(u2.invert());
        Word<Letter> newRoot = u2.concat(v2);
        Word<Letter> newRight = v2.invert().concat(rightMatching);

        newTile = new Tile(newRoot, newLeft, newRight, Direction.Positive);
        return newTile;
    }

    public Walk toWalk() {
        return (Walk) left.invert().concat(left).
                concat(root).
                concat(right).concat(right.invert());
    }

    @Override
    public String toString() {
        boolean neg = direction == Direction.Negative;
        return "(" + left + "," +
                (neg ? "~(" : "") + root + (neg ? ")" : "") + "," + right + ")";
    }

    /**
     * A tile string must be in format (left, root, right).
     * Direction may be specified by the letter ~. E.g:
     * (left, ~(root), right).
     * @param string
     * @return
     * @throws InvalidTileException
     */
    public static Tile parse(String string)
            throws InvalidTileException {
        // remove spaces
        if (string.contains(" "))
            string = string.replace(" ", "");

        int len = string.length();
        // remove left & right parentheses
        string = string.substring(1, len - 1);
        // split by delimiter ','
        String[] split = string.split(",");

        if (split.length != 3)
            throw new InvalidTileException();

        Word<Letter> left, root = null, right;
        Direction direc = Direction.Positive;

        //left constraint
        left = new WordImpl(toLetterList(split[0]));

        // root
        if (split[1].equals("1"))
            direc = Direction.Both;
        else if (split[1].startsWith("~")) {
            direc = Direction.Negative;
            // remove ~ and the pair ( )
            if (split[1].length() == 2) // e.g ~a
                split[1] = split[1].substring(1, 2);
            else // e.g ~(ab)
                split[1] = split[1].substring(2, split[1].length() - 1);
        }
        root = new WordImpl(toLetterList(split[1]));

        // right constraint
        right = new WordImpl(toLetterList(split[2]));
        return new Tile(root, left, right, direc);
    }

    /**
     * Split a string into a generic list of single letters
     * @param str
     * @return
     */
    private static List<Letter> toLetterList(String str) {
        List<Letter> lst = new ArrayList<Letter>();
        for (int i = 0; i < str.length(); i++) {
            lst.add(new Letter(str.charAt(i)));
        }
        return lst;
    }

    /*----------- GET/SET ------------*/
    public Direction direction() {
        return direction;
    }

    public void setDirection(Direction direction) {
        this.direction = direction;
    }

    public Word<Letter> root() {
        return root;
    }

    public void setRoot(Word<Letter> root) {
        this.root = root;
    }

    public Word<Letter> left() {
        return left;
    }

    public void setLeft(Word<Letter> left) {
        this.left = left;
    }

    public Word<Letter> right() {
        return right;
    }

    public void setRight(Word<Letter> right) {
        this.right = right;
    }
}
